﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;

namespace LyncServerExtension
{
    /// <summary>
    /// Class to represent the delegation policy used by the web service.
    /// </summary>
    [XmlRoot(ElementName = "DelegationPolicy")]
    public sealed class LyncDelegationPolicy
    {
        /// <summary>
        /// Represents the joker value to represents any account name, authorized owner, or authorized delegates.
        /// </summary>
        private const string ANY = "*";

        [XmlIgnore]
        private Dictionary<string, Dictionary<string, List<string>>> operatorMap;

        /// <summary>
        /// Gets or sets the delegation rules.
        /// </summary>
        /// <value>
        /// The delegation rules.
        /// </value>
        [XmlArrayItem(ElementName="DelegationRule")]
        public List<LyncDelegationRule> DelegationRules { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="LyncDelegationPolicy"/> class.
        /// </summary>
        public LyncDelegationPolicy()
        {
            DelegationRules = new List<LyncDelegationRule>();
        }

        /// <summary>
        /// Deserializes and cache a delegation policy from a file.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        /// <returns>The delegation policy.</returns>
        public static LyncDelegationPolicy Load(string filePath)
        {
             if (filePath == null)
                throw new ArgumentNullException("filePath");

             LyncDelegationPolicy self;

            using (var stream = File.OpenRead(filePath))
                self = (LyncDelegationPolicy)new XmlSerializer(typeof(LyncDelegationPolicy)).Deserialize(stream);

            self.operatorMap = new Dictionary<string,Dictionary<string,List<string>>>();
            foreach (var rule in self.DelegationRules)
            {
                if (string.IsNullOrEmpty(rule.OperatorAccountName))
                    throw new LyncServerExtensionException("Empty rule operator account name.");

                if (rule.AuthorizedDelegates.Count == 0)
                    continue;

                string operatorAccountName = rule.OperatorAccountName.ToLower();

                if (!self.operatorMap.ContainsKey(operatorAccountName))
                    self.operatorMap[operatorAccountName] = new Dictionary<string, List<string>>();

                if (string.IsNullOrEmpty(rule.AuthorizedOwnerSip))
                    throw new LyncServerExtensionException("Empty rule owner sip.");

                bool isAny = false;
                foreach (var delegateSip in rule.AuthorizedDelegates)
                {
                    if (string.IsNullOrEmpty(delegateSip))
                        throw new LyncServerExtensionException("Empty delegate sip.");

                    if (delegateSip == ANY)
                        isAny = true;
                }

                string authorizedOwnerSip = rule.AuthorizedOwnerSip.ToLower();
                self.operatorMap[operatorAccountName][authorizedOwnerSip] = isAny
                    ? new List<string>() { ANY }
                    : rule.AuthorizedDelegates.Select(sip => sip.ToLower()).ToList();
            }
            return self;
        }

        /// <summary>
        /// Determines whether the specified account name is authorized.
        /// </summary>
        /// <param name="accountName">Name of the account.</param>
        /// <returns>
        ///   <c>true</c> if the specified account name is authorized; otherwise, <c>false</c>.
        /// </returns>
        public bool IsAuthorized(string accountName)
        {
            return operatorMap.ContainsKey(accountName) || operatorMap.ContainsKey(ANY);
        }

        public bool IsAuthorizedDelegation(string operatorAccountName, string ownerSip, string delegateSip)
        {
            Dictionary<string, List<string>> ownerMap;
            if (!operatorMap.TryGetValue(operatorAccountName, out ownerMap) &&
                !operatorMap.TryGetValue(ANY, out ownerMap))
                return false;

            List<string> delegateSips;
            if (!ownerMap.TryGetValue(ownerSip, out delegateSips) &&
                !ownerMap.TryGetValue(ANY, out delegateSips))
                return false;

            delegateSip = delegateSip.ToLower();
            if ((delegateSip == ownerSip) ||
                (!delegateSips.Contains(delegateSip) &&
                !((delegateSips.Count == 1) && (delegateSips[0] == ANY))))
                return false;

            return true;
        }

        /// <summary>
        /// Filters the authorized owners for the given account name from the given user map.
        /// </summary>
        /// <param name="accountName">Name of the account.</param>
        /// <param name="userMap">The user map.</param>
        /// <returns>A sequence of users.</returns>
        public IEnumerable<LyncUser> FilterAuthorizedOwners(string accountName, Dictionary<string, LyncUser> userMap)
        {
            List<LyncUser> results = new List<LyncUser>();
            Dictionary<string, List<string>> ownerMap;
            LyncUser user;

            if (!operatorMap.TryGetValue(accountName, out ownerMap) &&
                !operatorMap.TryGetValue(ANY, out ownerMap))
                return results;

            if (ownerMap.ContainsKey(ANY))
                return userMap.Values;

            foreach (KeyValuePair<string, List<string>> p in ownerMap)
                if (userMap.TryGetValue(p.Key, out user))
                    results.Add(user);

            return results;
        }

        /// <summary>
        /// Filters the authorized delegates for the given account name and owner SIP from the given user map.
        /// </summary>
        /// <param name="accountName">Name of the account.</param>
        /// <param name="ownerSip">The owner sip.</param>
        /// <param name="userMap">The user map.</param>
        /// <returns>A sequence of users.</returns>
        public IEnumerable<LyncUser> FilterAuthorizedDelegates(string accountName, string ownerSip, Dictionary<string, LyncUser> userMap)
        {
            List<LyncUser> results = new List<LyncUser>();
            Dictionary<string, List<string>> ownerMap;
            List<string> delegateSips;
            LyncUser user;

            if (!operatorMap.TryGetValue(accountName, out ownerMap) &&
                !operatorMap.TryGetValue(ANY, out ownerMap))
                return results;

            if (!ownerMap.TryGetValue(ownerSip, out delegateSips) &&
                !ownerMap.TryGetValue(ANY, out delegateSips))
                return results;

            if ((delegateSips.Count == 1) && (delegateSips[0] == ANY))
                return userMap.Values.Where(u => u.Sip != ownerSip).ToList();

            foreach (var delegateSip in delegateSips)
                if ((delegateSip != ownerSip) && userMap.TryGetValue(delegateSip, out user))
                    results.Add(user);

            return results;
        }
    }
}
